package it.uniroma2.structure;

import it.uniroma2.structure.deploy.Deploy;
import it.uniroma2.wsdl.WSDL_BPEL;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;


public class Process implements BPEL_Element, Serializable{
	Node mainNode;
	Vector<LinkServiceNode> links;
	private int indexNs;
	private VariableVector variables;
	private Vector<PartnerLink> partnerLinks;
	private Vector<Import> imports;
	private String targetNamespace = "http://defaultName";
	private Hashtable<String, String> namespaces;
	private final String localWSDL_BPEL = "";
	private String partProcess = "payload";
	
	
//------------------   Class Process   --------------------//

//------------------   Constructor   --------------------//
	public Process(){
		indexNs = 0 ;
		this.links = new Vector<LinkServiceNode>();
		this.imports = new Vector<Import>();
		this.variables = new VariableVector();
		this.partnerLinks = new Vector<PartnerLink>();
		this.namespaces = new Hashtable<String, String>();
		this.mainNode = new Main_Sequence_Control("Main");
		
		this.namespaces.put("http://docs.oasis-open.org/wsbpel/2.0/process/executable", "bpel");
		if(this.targetNamespace.equals("http://defaultName")){
			this.namespaces.put(this.targetNamespace, "tns");
		}
		
		Variable varIn = this.createInputVariable();
		Variable varOut = this.createOutputVariable();
		PartnerLink pl = this.createClientPartnerLink();
		((Main_Sequence_Control)mainNode).setPartnerLink(pl);
		((Main_Sequence_Control)mainNode).setVariableReceive(varIn);
		((Main_Sequence_Control)mainNode).setVariableReply(varOut);
		
		
//		this.imports.addElement(new Import(null, this.targetNamespace, this.localWSDL_BPEL));
	}
	
	
	public Process(String targetNamespace){
		this();
		this.targetNamespace = targetNamespace;
		this.namespaces.put(this.targetNamespace, "tns");
	}
	
	
//	Add client partnerLink
	private PartnerLink createClientPartnerLink() {
		String partnerLT = nsWSDLBPEL + ":" + this.mainNode.getName();
		String roleClient = this.mainNode.getName() + "_" + rolePartnerLinkClient;
		PartnerLink partnerClient = new PartnerLink( namePartnerLinkClient, partnerLT, true, roleClient);
		this.partnerLinks.addElement(partnerClient);
		return partnerClient;
	}

	
//	Add variables input	
	private Variable createInputVariable() {
		String nameVarIn = nameVariableInput;
		String msgtypeIn = nsWSDLBPEL + ":" + this.mainNode.getName() + "_" + msgTypeVariableInput;
		Variable varIn = new VariableProcess(nameVarIn, this.mainNode.getName() + WSDL_BPEL.messageInput, this.partProcess, this.targetNamespace, this.namespaces.get(targetNamespace), this);
//		((VariableService)varIn).setMessageType(msgtypeIn);
//		((VariableService)varIn).setPart("payload");
		((Main_Sequence_Control)this.mainNode).getParameterIn().setVariable(varIn);
		variables.addElement(varIn);
		return varIn;
	}
	
	
//	Add variables output	
	private Variable createOutputVariable() {
		String nameVarOut = nameVariableOutput;
		String msgtypeOut = nsWSDLBPEL + ":" + this.mainNode.getName() + "_" + msgTypeVariableOutput;
		Variable varOut = new VariableProcess(nameVarOut, this.mainNode.getName() + WSDL_BPEL.messageOutput, this.partProcess, this.targetNamespace, this.namespaces.get(targetNamespace), this);
//		((VariableService)varOut).setMessageType(msgtypeOut);
//		((VariableService)varOut).setPart("payload");
		((Main_Sequence_Control)this.mainNode).getParameterOut().setVariable(varOut);
		variables.addElement(varOut);
		return varOut;
	}
	
	
	public void generateImportService(Service service){
		Import importService = new Import(service, service.getNamespace(), service.getName() + ".wsdl");
		this.imports.addElement(importService);
	}
	
	
	public Node createNode(String name, String type) throws NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{
		try {
			Class cls = Class.forName(type);
			Constructor constr = cls.getConstructor (new Class[] { String.class });
			Object obj = constr.newInstance (new Object[] { name });
			return (Node) obj;
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	public void addNode(Node father, Node previous, String nameNewNode, String typeNode){
		try {
			Node newNode = createNode(nameNewNode, typeNode);
			addNode(father, previous, newNode);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	
	public void addNode(Node father, Node previous, Node newNode){
		newNode.setFather(father);
		String newOrder = this.generateOrderId(father, previous);
		newNode.setOrderId(newOrder);
		int start = 0;
		if(previous != null){
			start = Integer.parseInt(previous.getOrderId().substring(previous.getOrderId().lastIndexOf("-")+1));
		}
		for(int i = start; i < father.subNodes.size(); i++){
			Node tempN = father.subNodes.get(i);
			tempN.incrementOrderId();
		}
		if(newNode instanceof Service){
			this.generateNamespaceService((Service)newNode);
			((Service)newNode).setCompatNS(this.namespaces.get(((Service) newNode).getNamespace()));
			this.generateVariableService((Service) newNode);
			this.generatePartnerLinkService((Service)newNode);
			this.generateImportService((Service)newNode);
		}
		father.subNodes.add(newNode);
		Collections.sort(father.subNodes, new ComparetorNode());
	}
	
	
	public Node getMainNode(){
		return this.mainNode;
	}
	
	
	public LinkServiceNode addLinkNode(Node from, Node to){
		if(from instanceof Service && to instanceof Service){
			LinkServiceNode newLinkNode = new LinkServiceNode("link", from, to);
			((Service) to).addInLink(newLinkNode);
			((Service) from).addOutLink(newLinkNode);
			return newLinkNode;
		} else if (from instanceof Service && to instanceof Reply) {
			LinkServiceNode newLinkNode = new LinkServiceNode("link", from, to);
			((Reply) to).addInLink(newLinkNode);
			((Service) from).addOutLink(newLinkNode);
			return newLinkNode;
		} else if (from instanceof Receive && to instanceof Service) {
			LinkServiceNode newLinkNode = new LinkServiceNode("link", from, to);
			((Service) to).addInLink(newLinkNode);
			((Receive) from).addOutLink(newLinkNode);
			return newLinkNode;
		}
		return null;
	}
	
	public LinkServiceNode addInputLink(Node to) {
		return addLinkNode(((Main_Sequence_Control)this.mainNode).getReceive(), to);
	}
	
	public LinkServiceNode addOutputLink(Node from) {
		return addLinkNode(from, ((Main_Sequence_Control)this.mainNode).getReply());
	}
	
	
	public void generateNamespaceService(Service service) {
		namespaces.put(service.getNamespace(), "ns" + this.indexNs);
		this.indexNs++;
	}
	
	
	public void generateVariableService(Service serviceNode){
		Vector<Parameter> parsIn = serviceNode.getParameterIn();
		Vector<Parameter> parsOut = serviceNode.getParameterOut();
		
		String msgIn = serviceNode.getInputMessage();
		String partIn = serviceNode.getPartIn();
		String msgOut = serviceNode.getOutputMessage();
		String partOut = serviceNode.getPartOut();
		
		String nameVarIn = "Request" + serviceNode.getName() + serviceNode.getOrderId();
		Variable varIn = new VariableService(nameVarIn, serviceNode, true);
		((VariableService)varIn).setAssociatedParameter(parsIn);
		((VariableService)varIn).setMessageType(msgIn);
		((VariableService)varIn).setPart(partIn);
		variables.addElement(varIn);
		for(Parameter tempPI : parsIn){
			tempPI.setVariable(varIn);
		}
		
		String nameVarOut = "Response" + serviceNode.getName() + serviceNode.getOrderId();
		Variable varOut = new VariableService(nameVarOut, serviceNode, true);
		((VariableService)varOut).setAssociatedParameter(parsOut);
		((VariableService)varOut).setMessageType(msgOut);
		((VariableService)varOut).setPart(partOut);
		variables.addElement(varOut);
		for(Parameter tempPO : parsOut){
			tempPO.setVariable(varOut);
		}

	}
	
	
	private boolean isContainPL(PartnerLink pl){
		for(PartnerLink temp : this.partnerLinks) {
			if(temp.equals(pl)) {
				return true;
			}
		}
		return false;
	}
	
	
	private void generatePartnerLinkService(Service serviceNode){
		String namePartner = serviceNode.getName() + tagPL;
		String partnerLT = nsWSDLBPEL + ":" + serviceNode.getName() + tagPLT;
		String rolePartner = serviceNode.getName() + tagRole;
		PartnerLink partner = new PartnerLink( namePartner, partnerLT, false, rolePartner);
		partner.setServiceRef(serviceNode);
		if(!this.isContainPL(partner)){
			this.partnerLinks.addElement(partner);
		}
		serviceNode.setPartnerLink(partner);
	}
	
	
	private String generateOrderId(Node father, Node previous){
		String fatherId = father.getOrderId();
		if(previous != null){
			String completePreviousId = previous.getOrderId();
			String previousId = completePreviousId.substring(fatherId.length() + 1);
			int id = Integer.parseInt(previousId);
			int newId = id + 1;
			return fatherId + "-" + newId;
		}else {
			return fatherId + "-1";
		}
		
	}
	
	
	public void removeNode(Service ser){
		ser.removeEveryLink();
		Node father = ser.getFatherNode();
		
		int start = Integer.parseInt(ser.getOrderId().substring(ser.getOrderId().lastIndexOf("-")+1));
		for(int i = start; i < father.subNodes.size(); i++){
			Node tempN = father.subNodes.get(i);
			tempN.decrementOrderId();
		}
		father.subNodes.remove(ser);
	}
	
	
	
//	Method that generates variables from a well-formed workflow
	/*public void generateVariable(){
		Vector<Node> nodes = new Vector<Node>();
		mainNode.getAllNodes(nodes);

//		Add variables input
		String nameVarIn = nameVariableInput;
		String msgtypeIn = nsWSDLBPEL + ":" + this.mainNode.getName() + "_" + msgTypeVariableInput;
		Variable varIn = new VariableService(nameVarIn, msgtypeIn, null);
		variables.addElement(varIn);

//		add variables request/response for each node service
		for(Node temp : nodes){
			if(temp instanceof Service){
				String namespaceService = ((Service) temp).getNamespace();
				Vector<Parameter> tempParIn = ((Service)temp).getParameterIn();
				Vector<Parameter> tempParOut = ((Service)temp).getParameterOut();
				for(Parameter tempPI : tempParIn){
					String nameVar = temp.getName() + "-" + tempPI.getName() + temp.getOrderId();
					String msgtype = namespaces.get(namespaceService) + ":" + temp.getName();
					Variable var = new VariableService(nameVar, msgtype, tempPI);
					tempPI.setVariable(var);
					variables.addElement(var);
				}
				for(Parameter tempPO : tempParOut){
					String nameVar = temp.getName()+"-"+tempPO.getName() + temp.getOrderId();
					String msgtype = namespaces.get(namespaceService) + ":" + temp.getName();
					Variable var = new VariableService(nameVar, msgtype, tempPO);
					tempPO.setVariable(var);
					variables.addElement(var);
				}
			}else if(temp instanceof Flow_Control){
				String nameVar = temp.getName()+"_var";
				String type = namespaces.get(nsXMLSchema) + ":string";
				Variable var = new VariableFlow(nameVar, type);
				variables.addElement(var);
			}
		}
		
//		Add variables output
		String nameVarOut = nameVariableOutput;
		String msgtypeOut = nsWSDLBPEL + ":" + this.mainNode.getName() + "_" + msgTypeVariableOutput;
		Variable varOut = new VariableService(nameVarOut, msgtypeOut, null);
		variables.addElement(varOut);
	}
	*/
	
	public void setMainNode(Node node){
		this.mainNode = node;
	}
	
	
	public void generateNamespaces(){
		Vector<Service> services = new Vector<Service>();
		mainNode.getServices(services);
		int index = 0;
		for(Service temp : services){
			namespaces.put(temp.getNamespace(), "ns" + index);
			index++;
		}
		namespaces.put(nsXMLSchema, "ns" + index);
		namespaces.put(this.targetNamespace, nsWSDLBPEL);
		namespaces.put(nsBPEL, nsBPELValue);
	}
	
	
	public void generatePartnerLink(){
		Vector<Service> services = new Vector<Service>();
		mainNode.getServices(services);
		
		//Add client partnerLink
		String partnerLT = nsWSDLBPEL + ":" + this.mainNode.getName();
		String roleClient = this.mainNode.getName() + "_" + rolePartnerLinkClient;
		PartnerLink partnerClient = new PartnerLink( namePartnerLinkClient, partnerLT, true, roleClient);
		this.partnerLinks.addElement(partnerClient);
		
		for(Service temp : services){
			String namePartner = temp.getName() + tagPL;
			partnerLT = nsWSDLBPEL + ":" + temp.getName() + tagPLT;
			String rolePartner = temp.getName() + tagRole;
			PartnerLink partner = new PartnerLink( namePartner, partnerLT, false, rolePartner);
			this.partnerLinks.addElement(partner);
			temp.setPartnerLink(partner);
		}
	}
	
	
	public String getTargetNamespace() {
		return targetNamespace;
	}


	public void setTargetNamespace(String targetNamespace) {
		this.targetNamespace = targetNamespace;
	}
	
	
	public String save(){
//		this.generateNamespaces();
//		this.generateVariable();
//		this.generatePartnerLink();
		return this.toBPEL();
		
	}
	
	
	public String writeBPELPartnerLink(){
		String output = "<" + partnerLinkTag + ">\n";
		for(PartnerLink temp : this.partnerLinks){
			output = output + temp.toBPEL();
		}
		output = output + "</" + partnerLinkTag + ">\n";
		return output;
	}
	
	
	
	public String writeImport(){
		
		String output = (new Import(null, this.targetNamespace, this.mainNode.getName() + "Artifact.wsdl")).toBPEL();
		for(Import temp : this.imports){
			output = output + temp.toBPEL();
		}
		return output;
	}
	
	
	public void stampEveryOrderId(){
		Vector<Node> nodes = new Vector<Node>();
		this.mainNode.getAllNodes(nodes);
		for(Node temp : nodes){
			System.out.println(temp.getClass() + "\t" + temp.getName() + "\t" + temp.getOrderId());
		}
	}
	
	
	public String toBPEL(){
		String output = "<bpel:process name=\"" + mainNode.getName() +"\"\n";
		output = output + "targetNamespace=\"" + targetNamespace + "\"\n";
		output = output + "suppressJoinFailure=\"yes\"\n";
		Enumeration<String> namespacesEnum = namespaces.keys();
		String namespaceKey;
		String namespaceValue;
		while(namespacesEnum.hasMoreElements()){
			namespaceKey = namespacesEnum.nextElement();
			namespaceValue = namespaces.get(namespaceKey);
			output = output + nsTag + namespaceValue + "=\"" + namespaceKey + "\"\n";
		}
		
		output = output.substring(0, output.length()-1) + ">\n";
		
		output = output + this.writeImport();
		output = output + this.writeBPELPartnerLink();
		output = output + variables.toBPEL();
		output = output + this.mainNode.toBPEL();
		output = output + "</bpel:process>";
		return output;
	}
	
	
	private int firstOccurrence(String sentence, char c){
		for(int i = 0; i<sentence.length(); i++){
			if(sentence.charAt(i) == c)return i;
		}
		return -1;
	}
	
	
	public void saveWSDL(){
		File wsdlFile = new File(this.mainNode.getName() + "Artifact.wsdl");
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(wsdlFile));
			WSDL_BPEL wsdl = new WSDL_BPEL(this.mainNode.getName(), this.targetNamespace);
			
			for(PartnerLink temp: this.partnerLinks){
				if(temp.getServiceRef() != null){
					String pltName = temp.getPartenerLinkType().substring(this.firstOccurrence(temp.getPartenerLinkType(), ':')+1);
//					System.out.println(pltName);
					wsdl.addPartnerLinkType(pltName, temp.getRole(), temp.getServiceRef().getPort() , temp.getServiceRef().getNamespace(), temp.getServiceRef().getName() + ".wsdl");
				}else{
					String pltName = temp.getPartenerLinkType().substring(this.firstOccurrence(temp.getPartenerLinkType(), ':')+1);
//					System.out.println(pltName);
					wsdl.addPartnerLinkType(pltName, temp.getRole(), this.mainNode.getName() , this.targetNamespace, this.mainNode.getName() + "Artifact.wsdl");
				}
			}
			
			wsdl.createWSDL();
			writer.write(wsdl.dumpWSDL());
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void saveBPEL(){
		File bpelFile = new File(this.mainNode.getName() + ".bpel");
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(bpelFile));
			String bpel = this.toBPEL();
			writer.write(bpel);
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	public void saveDeploy(){
		File deployFile = new File("deploy.xml");
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(deployFile));
			
			Deploy deploy = new Deploy(this.mainNode.getName(), this.targetNamespace);
			Iterator<PartnerLink> iterPL = this.partnerLinks.iterator();
			while(iterPL.hasNext()){
				PartnerLink pl = iterPL.next();
				if(pl.getServiceRef() == null){
//					deploy.addInvoke(pl.getName(), this.targetNamespace, this.mainNode.getName(),  "");
				}else{
					deploy.addInvoke(pl.getName(), pl.getServiceRef().getNamespace(), pl.getServiceRef().getServiceFromWSDL(),  pl.getServiceRef().getPort());
				}
			}
			writer.write(deploy.generateDeploy());
			
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	
	public void saveWorkflow(){
		this.saveBPEL();
		this.saveWSDL();
		this.saveDeploy();
	}
}
